package Server;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

import communication.ServerMessage;

import logic.Log;
import logic.OnlinePlayer;
import logic.PlayRequest;
import logic.User;

/**
 * @author Rodrigo
 *
 */
public class ServerData {

	public final static int LOGIN_OK = 11;
	public final static int LOGIN_INVALID_USERNAME = 12;
	public final static int LOGIN_INVALID_PASSWORD = 13;
	public final static int LOGIN_ALREADY_CONNECTED = 14;
	public final static int LOGIN_FULL = 15;
	
	public final static int MAX_USERS_ANCHIEVED = 21;
	
	private ArrayList<Log> logs = new ArrayList<Log>();
	
	private ArrayList<Player> players = new ArrayList<Player>();
	private ArrayList<User> users = new ArrayList<User>();
	private ArrayList<PlayRequest> playRequests = new ArrayList<PlayRequest>();
	private ArrayList<Game> games = new ArrayList<Game>();
	
	
	private String users_filename="users.db";
	@SuppressWarnings("unused")
	private int MAX_PLAYERS_ONLINE=50;
	@SuppressWarnings("unused")
	private int MAX_USERS=50;
	
	private Server srv;
	
	public ServerData(Server server) {
		srv = server;
	}
	
	/*-------------------------- GETTERS AND SETTERS ----------------------------------*/
	
	
	
	public String getUsers_filename() {
		return users_filename;
	}


	public ArrayList<Log> getLogs() {
		return logs;
	}

	public void setUsers_filename(String users_filename) {
		this.users_filename = users_filename;
	}

	public  ArrayList<User> getUsers() {
		return users;
	}


	public  void setUsers(ArrayList<User> users) {
		this.users = users;
	}
	
	
	public ArrayList<PlayRequest> getPlayRequests() {
		return playRequests;
	}

	public ArrayList<Game> getGames() {
		return games;
	}

	public ArrayList<Player> getPlayers() {
		return players;
	}
	
	
	public void log(String msg, int type){
		logs.add(new Log(msg,type));
	}
	
	
	/*--------------------- USER MANAGEMENT AND AUTHORIZATION --------------------*/
	
	@SuppressWarnings("unchecked")
	public void loadUsers(){
		
		    FileInputStream fis = null;
	        ObjectInputStream in = null;
	        	        
	        try
	        {
	            fis = new FileInputStream(users_filename);
	            in = new ObjectInputStream(fis);
	            users = (ArrayList<User>)in.readObject();
	            in.close();   
	        }
	        catch(IOException ex)
	        {
	        	System.out.println("Error loading users from file! File not found!");
	        	return;
	        }
	        catch(ClassNotFoundException ex)
	        {
	        	System.out.println("Error saving users from file!");
	        	return;
	        }
	        
		System.out.println("Users loaded sucessfully!");
	}
	
	public boolean saveUsers(){

        FileOutputStream fos = null;
        ObjectOutputStream out = null;
        
        try
        {
            fos = new FileOutputStream(users_filename);
            out = new ObjectOutputStream(fos);
            out.writeObject(users);
            out.close();
        }
            catch(IOException ex)
        {
            System.out.println("Error saving users to file!");
            return false;
        }
        
         return true;
	}
	
	public User getUserByUsername(String username){
		
		for (int i=0; i<users.size(); i++)
			if (users.get(i).getUserName().toUpperCase().equals(username.toUpperCase()))
				return users.get(i);
			
		return null;
	}
	
	public boolean isRegistered(String username){
		
		if (getUserByUsername(username)==null)		
			return false;
		else
			return true;
		
	}
	
	public Player getPlayerByUsername(String username){
		
		Player player = null;

		synchronized (players) {


			for (Player p : players)
				if (p.isLoggedIn())
					if (p.getUser().getUserName().toUpperCase().equals(username.toUpperCase())){
						player = p;
						break;
					}

		}

		return player;
	}
	
	//verifica se um determinado utilizador está ligado
	public boolean isConnected(String username){
		
		if (getPlayerByUsername(username)==null)
			return false;
		else
			return true;
	}
	
	//verifica se a combinação username/password é válida
	public boolean isValidLoginInfo(String username, String password){
		
		User usr = getUserByUsername(username);
		
		if (usr==null)
			return false;
		else
			if (usr.getPassword().equals(password))
				return true;
			else
				return false;
		
	}
	
	//adiciona novo utilizador á base de dados
	public boolean addUser(String username, String password){
		
		if (isRegistered(username)) 
			return false;
		
		User user = new User(username, password);
		users.add(user);
		saveUsers();
		
		return true;
		
	}
		
	/**
	 * 
	 * verifica se é válido e possível (ou não) um determinado login
	 */
	public int checkLogin(String username, String password){
		
		synchronized (users) {
			synchronized (players) {
				
				User usr = getUserByUsername(username);
				
				if (usr==null)
					return LOGIN_INVALID_USERNAME;
				
				if (!isValidLoginInfo(username, password))
					return LOGIN_INVALID_PASSWORD;
				
				if (isConnected(username))
					return LOGIN_ALREADY_CONNECTED;
				
				return LOGIN_OK;
				
			}
		}
		
	}
	
	/**
	 * 
	 * Notifica os jogadores que um determinado jogador fez logout
	 */
	public boolean logout(Player player){

		synchronized (players) {

			for (Player p : players)
				if (p!=player){

					ServerMessage msg = new ServerMessage(ServerMessage.MSG_PLAYER_OUT);
					msg.setUsername(player.getUser().getUserName());

					player.sendMessage(msg);
					

				}


			logs.add(new Log("Player " + player.getUser().getUserName() + " logged out!", Log.INFO));
			players.remove(player);
			
		}

		srv.getRmiNavalBattle().updateUsers(srv);
		srv.requestUpdate();	

		return true;
	}

	/**
	 * 
	 *  Notifica os jogadores que um determinado jogador fez login
	 */
	public boolean login(Player player){

		logs.add(new Log("Player " + player.getUser().getUserName() + " logged in!", Log.INFO));
		sendUpdateAll();
		srv.requestUpdate();
		
		
		srv.getRmiNavalBattle().updateUsers(srv);

		return false;
	}
	
	//fecha os input/output streams, fecha o socket e termina a thread de um determinado jogador
	private void _disconnect(Player player){

		try{
			player.getIn().close();
		}catch (IOException ex){}

		try{
			player.getOut().close();
		}catch(IOException ex){};

		try{
			player.getSocket().close();
		}catch(IOException ex){}

		try{
			player.requestStop();
			player.join(2000);
		}catch(InterruptedException ex){
			//p.getHandler().stop();
		};

		logs.add(new Log("Player " + player.getUser().getUserName() + " disconnected!", Log.WARNING));
		players.remove(player);

}

	//faz disconnect de um jogador sem alertar os outros jogadores
	public boolean disconnect(Player player){
		
		synchronized (players) {
			_disconnect(player);
		}
		
		srv.getRmiNavalBattle().updateUsers(srv);
		
		return true;
	}
	
	//faz disconnect de um jogador e alerta os outros jogadores
	public boolean disconnectAndUpdate(Player p){
		
		synchronized (players) {
			_disconnect(p);	
		}
		
		sendUpdateAll();
		
		srv.getRmiNavalBattle().updateUsers(srv);
		
		return true;		
	}
	
	//desliga todos os jogadores ligados
	public void disconnectAll(){
		
		synchronized (players) {
			while (players.size()>0){
				_disconnect(players.get(0));
			}		
		}
		
	}
	
	/*--------------------------- MESSAGES AND COMMUNICATION  -------------------------------*/
	
	public void sendUpdateAll(){
		
		synchronized (players) {
			for (Player p : players){
				
				if (p.getPlayerState()==Player.STATE_CONNECTED || p.getPlayerState()==Player.STATE_WAITING)
					p.sendMessage(getUpdateMessageTo(p));
				
				
			}
		}
			
	}
	
	//envia uma mensagem de actualização de dados dos jogadores activos a um determinado jogador
	public void sendUpdateTo(Player p){
		
		synchronized (players) {
			
				if (p.getPlayerState()==Player.STATE_CONNECTED || p.getPlayerState()==Player.STATE_WAITING)
					p.sendMessage(getUpdateMessageTo(p));
				
		}
			
	}
	
	public ServerMessage getUpdateMessageTo(Player player){
		
		ServerMessage msg = new ServerMessage(ServerMessage.MSG_UPDATE_ALL);
		
		synchronized (players) {
			synchronized (users) {

				for (Player p: players)
					if (p!=player){

						OnlinePlayer op = new OnlinePlayer();

						op.username = p.getUser().getUserName();
						op.state = p.getPlayerState();

						if (op.state==Player.STATE_PLAYING)
							op.enemy = p.getGame().getEnemyOf(p).getUser().getUserName();

						msg.getOnlinePlayers().add(op);
					}
			}


		}
			
		
		return msg;
	}
	
	/*---------------------------- INVITATIONS / PLAY REQUESTS  --------------------------------*/
	
	PlayRequest getPlayRequest(String from, String to){
		
		for (int i=0; i<playRequests.size(); i++){
			if (playRequests.get(i).getFrom().toUpperCase().equals(from.toUpperCase()))			
				if (playRequests.get(i).getTo().toUpperCase().equals(to.toUpperCase()))
					return playRequests.get(i);	
		}
			
		return null;	
		
	}

	//cria novo jogo
	Game createGame(Player p1, Player p2){

		Game game = null;

		synchronized (players) {
			synchronized (playRequests) {

				if (p1.getPlayerState()!=Player.STATE_PLAYING && p2.getPlayerState()!=Player.STATE_PLAYING){
					
					game = new Game(p1, p2, this.srv);
					p1.setPlayerState(Player.STATE_PLAYING);
					p2.setPlayerState(Player.STATE_PLAYING);
					game.start();
					games.add(game);

				}
			}
		}
		
		srv.getRmiNavalBattle().updateGames(srv);
		srv.getRmiNavalBattle().updateUsers(srv);
		
		return game;
		
	}

	//envia mensagem de chat
	public void sendChatMsg(Player player, String message) {
		
		ServerMessage msg = new ServerMessage(ServerMessage.MSG_CHAT_MSG);
		msg.setUsername(player.getUser().getUserName());
		msg.setMsg(message);
		
		synchronized (players) {
			
			for (Player p : players)
				if (p.getPlayerState()!=Player.STATE_PLAYING){
					p.sendMessage(msg);				
				}
			
		}
		
	}

	
	
}
